package netcode.server;

import gamePlay.Bet;
import gamePlay.Question;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListMap;

import crawler.QuestionDBConnector;

/**
 * This class controls the game play of a networked game on the server
 * 
 * @author Jim Jensen
 * @version April 12, 2012
 */
public class NetworkGame extends Thread {

	private int gameID;
	private int numRounds;
	private String gameName;

	// Map of client keys to ConnectedClients
	private Map<Integer, ConnectedClient> clients;
	
	// map of client keys to a list of all that player's bets
	private Map<Integer, ArrayList<Bet>> bets;

	private String[] qCategories;
	private long timeToGuess;
	private long timeToBet;
	private Question currentQuestion;

	// map of client keys to guess
	private Map<Integer, Integer> guessMap;
	private List<Question> questionList;
	private ArrayList<Integer> oddsList;
	private ArrayList<Integer> sortedGuesses;

	public NetworkGame() {
	}

	public NetworkGame(int gID, String gName, int cKey, ConnectedClient client,
			String[] categories, int rounds) {

		gameID = gID;

		gameName = gName;

		clients = new ConcurrentSkipListMap<Integer, ConnectedClient>();

		clients.put(cKey, client);

		bets = new ConcurrentSkipListMap<Integer, ArrayList<Bet>>();

		bets.put(cKey, new ArrayList<Bet>());

		qCategories = categories;

		numRounds = rounds;

		timeToGuess = 0;

		timeToBet = 0;

		sortedGuesses = new ArrayList<Integer>();
		
		guessMap = new ConcurrentSkipListMap<Integer, Integer>();
		oddsList = new ArrayList<Integer>();

		grabQuestions();

	}

	public void run() {
		playGame();
	}

	/**
	 * Calling this method starts the game
	 */
	private void playGame() {
		for (int i = 0; i < numRounds; i++) {
			currentQuestion = questionList.get(i);

			// send question to players
			Iterator<Integer> it = clients.keySet().iterator();
			while (it.hasNext()) {
				Server.sendQuestion(it.next(), currentQuestion.getText());
			}

			// get answers (Server class handles this)
			// players get 30 seconds to guess, plus 200 ms allowed for latency
			timeToGuess = System.currentTimeMillis() + 30200;
			sleepThread(timeToGuess);

			// calculate odds
			oddsCalc();

			// send odds to players
			it = clients.keySet().iterator();
			while (it.hasNext()) {
				Server.sendBettingOdds(it.next(), gameID, oddsList, sortedGuesses);
			}

			// get bets from players (Server class handles this)
			// players get 30 seconds to bet, plus 200 ms allowed for latency
			timeToBet = System.currentTimeMillis() + 30200;
			sleepThread(timeToBet);

			// calculate and deal winnings and send round info
			winnings();

			// clear answers, bets, odds
			clearAll();

			// give the players 30 seconds to review the rounds stats
			// before starting the next round
			sleepThread(System.currentTimeMillis() + 30000);
		}

		sendGameResults();
	}

	/**
	 * Adds a new ConnectedClient to the game, and sends the client ID of the
	 * new player to all the players.
	 * 
	 * @param cKey
	 * @param newPlayer
	 */
	public void addClient(int cKey, ConnectedClient client) {
		if (!clients.containsKey(cKey)) {
			System.out.println("Adding client that's not already in game.");
			clients.put(cKey, client);
			System.out.println("Client added");
			bets.put(cKey, new ArrayList<Bet>());
			System.out.println("Bets initialized");

			System.out
					.println("Sending game lobby info to all connected clients");
			Iterator<Integer> clientKeys = clients.keySet().iterator();
			while (clientKeys.hasNext()) {
				int clientKey = clientKeys.next();
				Server.sendGameLobbyInfo(clientKey, gameID);
			}
			System.out.println("Sent info.");
		}
	}

	/**
	 * Removes a ConnectedClient from the game, then sends the key of the player
	 * that left to the rest of the players.
	 * 
	 * @param cKey
	 */
	public void removeClient(int cKey) {
		if (clients.containsKey(cKey)) {
			clients.remove(cKey);
			bets.remove(cKey);

			Iterator<Integer> clientKeys = clients.keySet().iterator();
			while (clientKeys.hasNext()) {
				Server.sendGameLobbyInfo(clientKeys.next(), gameID);
			}
		}
	}

	/**
	 * Currently populates questionList with as many random questions as there
	 * are rounds
	 */
	private void grabQuestions() {
		QuestionDBConnector dbConnection = new QuestionDBConnector();
		try {
			dbConnection.initialize("jdbc:mysql://localhost/groupwork_db",
					"groupwork", "smiles");
			questionList = dbConnection.getRandomQuestions(numRounds);
			dbConnection.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Registers the player's answer
	 * 
	 * @param clientID
	 *            - the player's id
	 * @param answer
	 *            - the player's answer for the current question
	 */
	public void clientSubmitAnswer(int cKey, int clientGuess) {
		// only register guesses if submitted before time expired
		if (System.currentTimeMillis() < timeToGuess) {
			//(Anthony) changed this to map. Otherwise, you won't know who submitted what.
			if(!guessMap.containsKey(cKey)){
				guessMap.put(cKey, clientGuess);
			}else{
				// Send some sort of error message.
			}
			
			if (guessMap.size() == clients.size())
				notify();

			// sleep the thread until time runs out if all players have not
			// guessed
			// (Anthony) This causes the server thread to sleep, which is no
			// bueno.
			// if (guesses.size() < clients.keySet().size()) {
			// sleepThread(timeToGuess);
			// }
		}
	}

	/**
	 * Gets a list of bets from a client and puts them in the Player object
	 * 
	 * @param cKey
	 *            client's key
	 * @param bet
	 *            client's bets
	 */
	public void clientSubmitBet(int cKey, ArrayList<Bet> bet) {
		// only register bets if submitted before time expired
		if (System.currentTimeMillis() < timeToBet) {
			bets.put(cKey, bet);

			
			if (bets.size() == clients.size())
				notify();
			
			// sleep the thread until time runs out if all players have not
			// placed bets
			// (Anthony) This would put the server to sleep.
//			if (guessMap.size() < clients.keySet().size()) {
//				sleepThread(timeToBet);
//			}
		}
	}

	// method for calculating the odds on each bet is tiered
	// By Team Good Team, modified by Jim J.
	private void oddsCalc() {
		int total = 0;
		System.out.println("Entered oddsCalc.");
		Iterator<Integer> gIterator = guessMap.keySet().iterator();
		System.out.println("guessMap Iterator created.");
		while (gIterator.hasNext()) {
			System.out.println("entered the while loop");
			int currentGuess = guessMap.get(gIterator.next());
			System.out.println("currentGuess: " + currentGuess);
			total += currentGuess;
			System.out.println("total: " + total);
			sortedGuesses.add(currentGuess);
			System.out.println("Added to sortedGuesses");
		}

		Collections.sort(sortedGuesses);
		System.out.println("Sorted sortedGuesses, heh");
		for (int i = 0; i < sortedGuesses.size(); i++) {
			System.out.println("in the for loop");
			if (sortedGuesses.get(i) != 0) {
				oddsList.add(total / sortedGuesses.get(i));
				System.out.println("Added to oddsList");
			} else {
				oddsList.add(5);
				System.out.println("Added to oddsList");
			}
		}

		// (Anthony) was getting Exceptions
		// oddsList.add(total / sortedGuesses.get(0));
		// oddsList.add(total / sortedGuesses.get(1));
		//
		// if (guessSet.size() > 4) {
		// oddsList.add(total / sortedGuesses.get(2));
		// }
	}

	/*
	 * Calculates results from bets To be used after all bets are in By Good
	 * Team, modified by Jim J.
	 */
	public void winnings() {
		int answer = currentQuestion.getAnswer();
		int winIndex = 0;

		// holds players and their round payout in the format:
		// {p1, p1's payout, p2, p2's payout, ... }
		int[] playersAndWinnings = new int[clients.size() * 2];
		int pwCounter = 0;

		// the winning guess will be the largest number less than or equal to
		// the answer
		for (int i = 1; i < sortedGuesses.size(); i++) {
			if (sortedGuesses.get(i) <= answer) {
				winIndex = i;
			}
		}

		// cycle through all the players
		Iterator<Integer> keyIterator = clients.keySet().iterator();
		while (keyIterator.hasNext()) {
			int currentKey = keyIterator.next();

			// running total of chips won/lost by the player identified by
			// currentKey
			int payout = 0;
			ArrayList<Bet> playerBets = bets.get(currentKey);

			// cycle through all of the current player's bets
			for (int i = 0; i < playerBets.size(); i++) {
				Bet currentBet = playerBets.get(i);
				int betChoice = currentBet.getChoice();

				if (betChoice == winIndex) {
					payout += (currentBet.getPermaChips() + currentBet
							.getGameChips()) * oddsList.get(winIndex);
				}

				else {
					payout -= currentBet.getGameChips();
				}
			}

			// get the player's old score, add this round's payout and update
			int score = clients.get(currentKey).getPlayer().getCurr_score();
			clients.get(currentKey).getPlayer().setCurr_score(score + payout);

			// store players winnings for later
			playersAndWinnings[pwCounter] = currentKey;
			playersAndWinnings[pwCounter + 1] = payout;
			pwCounter += 2;
		}

		for (int i = 0; i < playersAndWinnings.length; i += 2)
			Server.sendRoundResults(playersAndWinnings[i], gameID,
					currentQuestion.getAnswer(), playersAndWinnings);

	}

	// send final stats to all players
	private void sendGameResults() {
		Iterator<Integer> it = clients.keySet().iterator();
		int[] clientKeys = new int[clients.keySet().size()];
		int i = 0;

		while (it.hasNext()) {
			clientKeys[i] = it.next();
			i++;
		}

		// sends results to all clients one-by-one
		for (int j = 0; j < clientKeys.length; j++) {
			Server.sendGameResults(it.next(), gameID, clientKeys);
		}
	}

	// clears all stats for current round
	private void clearAll() {
		guessMap.clear();
		oddsList.clear();
		sortedGuesses.clear();

		Iterator<Integer> it = bets.keySet().iterator();
		while (it.hasNext()) {
			bets.get(it.next()).clear();
		}

//		it = guesses.keySet().iterator();
//		while (it.hasNext()) {
//			guesses.remove(it.next());
//		}
	}

	/**
	 * Sleeps the thread until the current part of the game is done
	 * 
	 * @param targetTime
	 *            time to wake up
	 */
	private void sleepThread(long targetTime) {
		long currentTime = System.currentTimeMillis();

		try {
			wait(targetTime - currentTime);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public int getGameID() {
		return gameID;
	}

	public String getGameName() {
		return gameName;
	}

	public Map<Integer, ConnectedClient> getConnectedClients() {
		return clients;
	}

	public String[] getCategories() {
		return qCategories;
	}

}
